//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <core.h>
#include <bsp.h>
#include <_hal.h>

#include "instruction.h"
#include "exception.h"

int DelaySlot(InterruptContext *pContext)
{
    return pContext->cp0_cause & CAUSEF_BD;
}

int EmulateLoadStoreInsn(InterruptContext *pContext,
    unsigned long addr, unsigned long pc)
{
    union unionMipsInstruction  insn;
    uint32_t                    uValue;

    assert(0 == pContext->regs[0]);

    //
    // This load never faults.
    //
    insn.word = *(unsigned int *)pc;

    switch (insn.i_format.opcode) {
    //
    // These are instructions that a compiler doesn't generate.  We
    // can assume therefore that the code is MIPS-aware and
    // really buggy.  Emulating these instructions would break the
    // semantics anyway.
    //
    case ll_op:
    case lld_op:
    case sc_op:
    case scd_op:

    //
    // For these instructions the only way to create an address
    // error is an attempted access to kernel/supervisor address
    // space.
    //
    case ldl_op:
    case ldr_op:
    case lwl_op:
    case lwr_op:
    case sdl_op:
    case sdr_op:
    case swl_op:
    case swr_op:
    case lb_op:
    case lbu_op:
    case sb_op:
        goto fault;

    /*
     * The remaining opcodes are the ones that are really of interest.
     */
    case lh_op:
        CHECK_DATA_NOT_ALIGN(addr, 0x1);

        int16_t     n16Value;
#if defined(_GNUC)
        ASM(
            ".set   noat;"
        "1:"
            "lb     %0, 1(%2);"
        "2:"
            "lbu    $1, 0(%2);"
            "sll    %0, 0x8;"
            "or     %0, $1;"
            "li     %1, 0;"
        "3:"
            ".set   at;"
            ".section   .fixup,\"ax\";"
        "4:"
            "li     %1, %3;"
            "j      3b;"
            ".previous;"
            ".section   __ex_table,\"a\";"
            ".word  1b, 4b;"
            ".word  2b, 4b;"
            ".previous;"
            : "=&r" (uValue), "=r" (res)
            : "r" (addr), "i" (-EFAULT));
#elif defined(_EVC)
        ASM(
            "lb     t4, 0x1(%1);"
            "lbu    t5, 0x0(%1);"
            "sll    t4, t4, 0x8;"
            "or     t4, t4, t5;"
            "sw     t4, 0(%0);", &n16Value, addr);
#else
#error unknown compiler
#endif

        pContext->regs[insn.i_format.rt] = (uint32_t)(int32_t)n16Value;

        break;

    case lw_op:
        CHECK_DATA_NOT_ALIGN(addr, 0x3);

#if defined(_GNUC)
        ASM(
            ".set   noat;"
        "1:"
            "lbu    %0, 3(%2);"
        "2:"
            "lbu    $1, 2(%2);"
            "sll    %0, 8;"
            "or     %0, $1;"
        "3:"
            "lbu    $1, 1(%2);"
            "sll    %0, 8;"
            "or     %0, $1;"
        "4:"
            "lbu    $1, 0(%2);"
            "sll    %0, 8;"
            "or     %0, $1;"
            "li     %1, 0;"
        "5:"
            ".set   at;"
            "\t.section\t.fixup,\"ax\"\n\t"
        "6:"
            "li     %1, %3;"
            "j      5b;"
            ".previous;"
            ".section\t__ex_table,\"a\"\n\t"
            ".word  1b, 6b;"
            ".word  2b, 6b;"
            ".word  3b, 6b;"
            ".word  4b, 6b;"
            ".previous"
            : "=&r" (uValue), "=r" (res)
            : "r" (addr), "i" (-EFAULT));
#elif defined(_EVC)
        ASM(
            "lbu    t4, 0x03(%1);"
            "lbu    t3, 0x02(%1);"
            "sll    t4, t4, 0x08;"
            "or     t4, t4, t3;"
            "lbu    t3, 0x01(%1);"
            "sll    t4, t4, 0x08;"
            "or     t4, t4, t3;"
            "lbu    t3, 0x00(%1);"
            "sll    t4, t4, 0x08;"
            "or     t4, t4, t3;"
            "sw     t4, 0x00(%0);", &uValue, addr);
#else
#error unknown compiler
#endif

        pContext->regs[insn.i_format.rt] = uValue;

        break;

    case lhu_op:
        CHECK_DATA_NOT_ALIGN(addr, 0x1);

#if defined(_GNUC)
        ASM(
            ".set   noat;"
        "1:"
            "lbu    %0, 1(%2);"
        "2:"
            "lbu    $1, 0(%2);"
            "sll    %0, 0x8;"
            "or     %0, $1;"
            "li     %1, 0;"
        "3:"
            ".set   at"
            ".section   .fixup,\"ax\";"
        "4:"
            "li     %1, %3;"
            "j      3b;"
            ".previous;"
            ".section   __ex_table,\"a\";"
            ".word  1b, 4b;"
            ".word  2b, 4b;"
            ".previous;"
            : "=&r" (uValue), "=r" (res)
            : "r" (addr), "i" (-EFAULT));
#elif defined(_EVC)
        ASM(
            "lbu    t4, 0x01(%1);"
            "lbu    t3, 0x00(%1);"
            "sll    t4, t4, 0x08;"
            "or     t4, t4, t3;"
            "sw     t4, 0x00(%0);", &uValue, addr);
#else
#error unknown compiler
#endif

        pContext->regs[insn.i_format.rt] = uValue;

        break;

    case lwu_op:
    case ld_op:
        /* Cannot handle 64-bit instructions in 32-bit kernel */
        goto fault;

    case sh_op:
        CHECK_DATA_NOT_ALIGN(addr, 0x1);

        uValue = pContext->regs[insn.i_format.rt];
#if defined(_GNUC)
        ASM(
            ".set   noat;"
        "1:"
            "sb     %1, 0(%2);"
            "srl    $1, %1, 0x8;"
        "2:"
            "sb     $1, 1(%2);"
            ".set   at;"
            "li     %0, 0;"
        "3:"
            "li     %0, %3;"
            "j      3b;"
            ".previous;"
            ".section   __ex_table,\"a\";"
            ".word  1b, 4b;"
            ".word  2b, 4b;"
            ".previous"
            : "=r" (res)
            : "r" (uValue), "r" (addr), "i" (-EFAULT));
#elif defined(_EVC)
        ASM(
            "sb     %0, 0x00(%1);"
            "srl    t4, %0, 0x08;"
            "sb     t4, 0x01(%1);", uValue, addr);
#else
#error unknown compiler
#endif

        break;

    case sw_op:
        CHECK_DATA_NOT_ALIGN(addr, 0x3);

        uValue = pContext->regs[insn.i_format.rt];
#if defined(_GNUC)
        ASM(
            ".set   noat;"
        "1:"
            "sb     %1, 0(%2);"
            "srl    $1, %1, 8;"
        "2:"
            "sb     $1, 1(%2);"
            "srl    $1, $1, 8;"
        "3:"
            "sb     $1, 2(%2);"
            "srl    $1, $1, 8;"
        "4:"
            "sb     $1, 3(%2);"
            "li     %0, 0;"
        "5:"
            ".set   at;"
            ".section   .fixup,\"ax\";"
        "6:"
            "li     %0, %3;"
            "j      5b;"
            ".previous;"
            ".section   __ex_table,\"a\";"
            ".word  1b, 6b;"
            ".word  2b, 6b;"
            ".word  3b, 6b;"
            ".word  4b, 6b;"
            ".previous;"
            : "=r" (res)
            : "r" (uValue), "r" (addr), "i" (-EFAULT));
#elif defined(_EVC)
        ASM(
            "sb     %0, 0x00(%1);"
            "srl    t4, %0, 0x08;"
            "sb     t4, 0x01(%1);"
            "srl    t4, t4, 0x08;"
            "sb     t4, 0x02(%1);"
            "srl    t4, t4, 0x08;"
            "sb     t4, 0x03(%1);", uValue, addr);
#else
#error unknown compiler
#endif

        break;

    case sd_op:
        kprintf("Cannot handle 64-bit instructions in 32-bit kernel\n");
        goto fault;

    case lwc1_op:
    case swc1_op:
        goto fault;

    case ldc1_op:
#ifdef _NOFLOAT
        ASM("break 0;");
#else
        CHECK_DATA_NOT_ALIGN(addr, 0x4);
        assert(insn.i_format.rt <= 30);

        switch (insn.i_format.rt) {
        case 0:
            ASM("lwc1   $f0, 0x0(%0);"
                "lwc1   $f1, 0x4(%0);", addr);
            break;
        case 2:
            ASM("lwc1   $f2, 0x0(%0);"
                "lwc1   $f3, 0x4(%0);", addr);
            break;
        case 4:
            ASM("lwc1   $f4, 0x0(%0);"
                "lwc1   $f5, 0x4(%0);", addr);
            break;
        case 6:
            ASM("lwc1   $f6, 0x0(%0);"
                "lwc1   $f7, 0x4(%0);", addr);
            break;
        case 8:
            ASM("lwc1   $f8, 0x0(%0);"
                "lwc1   $f9, 0x4(%0);", addr);
            break;
        case 10:
            ASM("lwc1   $f10, 0x0(%0);"
                "lwc1   $f11, 0x4(%0);", addr);
            break;
        case 12:
            ASM("lwc1   $f12, 0x0(%0);"
                "lwc1   $f13, 0x4(%0);", addr);
            break;
        case 14:
            ASM("lwc1   $f14, 0x0(%0);"
                "lwc1   $f15, 0x4(%0);", addr);
            break;
        case 16:
            ASM("lwc1   $f16, 0x0(%0);"
                "lwc1   $f17, 0x4(%0);", addr);
            break;
        case 18:
            ASM("lwc1   $f18, 0x0(%0);"
                "lwc1   $f19, 0x4(%0);", addr);
            break;
        case 20:
            ASM("lwc1   $f20, 0x0(%0);"
                "lwc1   $f21, 0x4(%0);", addr);
            break;
        case 22:
            ASM("lwc1   $f22, 0x0(%0);"
                "lwc1   $f23, 0x4(%0);", addr);
            break;
        case 24:
            ASM("lwc1   $f24, 0x0(%0);"
                "lwc1   $f25, 0x4(%0);", addr);
            break;
        case 26:
            ASM("lwc1   $f26, 0x0(%0);"
                "lwc1   $f27, 0x4(%0);", addr);
            break;
        case 28:
            ASM("lwc1   $f28, 0x0(%0);"
                "lwc1   $f29, 0x4(%0);", addr);
            break;
        case 30:
            ASM("lwc1   $f30, 0x0(%0);"
                "lwc1   $f31, 0x4(%0);", addr);
            break;
        default:
            goto fault;
        }
#endif

        break;

    case sdc1_op:
#ifdef _NOFLOAT
        ASM("break 0;");
#else
        CHECK_DATA_NOT_ALIGN(addr, 0x4);

        assert(insn.i_format.rt <= 30);

        switch (insn.i_format.rt) {
        case 0:
            ASM("swc1   $f0, 0x0(%0);"
                "swc1   $f1, 0x4(%0);", addr);
            break;
        case 2:
            ASM("swc1   $f2, 0x0(%0);"
                "swc1   $f3, 0x4(%0);", addr);
            break;
        case 4:
            ASM("swc1   $f4, 0x0(%0);"
                "swc1   $f5, 0x4(%0);", addr);
            break;
        case 6:
            ASM("swc1   $f6, 0x0(%0);"
                "swc1   $f7, 0x4(%0);", addr);
            break;
        case 8:
            ASM("swc1   $f8, 0x0(%0);"
                "swc1   $f9, 0x4(%0);", addr);
            break;
        case 10:
            ASM("swc1   $f10, 0x0(%0);"
                "swc1   $f11, 0x4(%0);", addr);
            break;
        case 12:
            ASM("swc1   $f12, 0x0(%0);"
                "swc1   $f13, 0x4(%0);", addr);
            break;
        case 14:
            ASM("swc1   $f14, 0x0(%0);"
                "swc1   $f15, 0x4(%0);", addr);
            break;
        case 16:
            ASM("swc1   $f16, 0x0(%0);"
                "swc1   $f17, 0x4(%0);", addr);
            break;
        case 18:
            ASM("swc1   $f18, 0x0(%0);"
                "swc1   $f19, 0x4(%0);", addr);
            break;
        case 20:
            ASM("swc1   $f20, 0x0(%0);"
                "swc1   $f21, 0x4(%0);", addr);
            break;
        case 22:
            ASM("swc1   $f22, 0x0(%0);"
                "swc1   $f23, 0x4(%0);", addr);
            break;
        case 24:
            ASM("swc1   $f24, 0x0(%0);"
               "swc1   $f25, 0x4(%0);", addr);
            break;
        case 26:
            ASM("swc1   $f26, 0x0(%0);"
                "swc1   $f27, 0x4(%0);", addr);
            break;
        case 28:
            ASM("swc1   $f28, 0x0(%0);"
                "swc1   $f29, 0x4(%0);", addr);
            break;
        case 30:
            ASM("swc1   $f30, 0x0(%0);"
                "swc1   $f31, 0x4(%0);", addr);
            break;
        default:
            goto fault;
        }
#endif  // _NOFLOAT

        break;

    case lwc2_op:
    case ldc2_op:
    case swc2_op:
    case sdc2_op:
        /*
         * These are the coprocessor 2 load/stores.  The current
         * implementations don't use cp2 and cp2 should always be
         * disabled in c0_status.  So send SIGILL.
                 * (No longer true: The Sony Praystation uses cp2 for
                 * 3D matrix operations.  Dunno if that thingy has a MMU ...)
         */
    default:
        /*
         * Pheeee...  We encountered an yet unknown instruction or
         * cache coherence problem.  Die sucker, die ...
         */
        goto fault;
    }

    return 0;

fault:
    Cli();
    kprintf("Unhandled kernel unaligned access or invalid instruction\n");
    kprintf("The instruction is 0x%08x\n", *(unsigned int *)pc);
    DumpCxt(pContext);
    myChat(pContext);

    return 0;
}

int ComputeReturnEpc(InterruptContext *pContext)
{
    if (!DelaySlot(pContext)) {
        pContext->cp0_epc += 4;
    }
    else {
        ReturnInstructionInDelaySlot(pContext);
    }

    return 0;
}
